package sadi.server;

import java.io.*;
import java.net.*;
import java.util.*;

import sadi.util.CardsCollection;

public class Player extends BlackJackers
{
	//Instance Variables
	private String cards[][] = new String[MAX_HANDS][MAX_CARDS];
	private int currentHand, totalHands;
	private int deckTotal[] = new int[MAX_HANDS],  //total value of cards in player hand(s)
				totalCards[] = new int[MAX_HANDS]; //total number of cards in player hand(s)
	private Socket socket;
	private DataOutputStream toPlayer;
	private DataInputStream fromPlayer;
	private ObjectOutputStream objectToPlayer;
	
	public Player(Socket socket, String username, int status)
	{
		//Initialize Variables
		super(username, status);
		this.socket = socket;
		currentHand = 0;
		totalHands = 1;
		
		for(int i=0; i<MAX_HANDS; i++)
		{
			deckTotal[i] = 0;
			totalCards[i] = 0;
		}
		
		try
		{
			//Create I/O Streams
			toPlayer = new DataOutputStream(socket.getOutputStream());
			fromPlayer = new DataInputStream(socket.getInputStream());
			objectToPlayer = new ObjectOutputStream(toPlayer);
		}
		catch(IOException e)
		{
			System.out.println("Unable to initialize " + getUsername() + ".");
			//e.printStackTrace();
		}
	}
	
	
	
	//Check Whether Player Connection Is Active Or Not
	//Close Socket If Inactive
	public boolean isActive()
	{
		try
		{
			toPlayer.writeBoolean(true);
			socket.setSoTimeout(5000);
			fromPlayer.readBoolean();
			socket.setSoTimeout(0);
			return true;
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ioe)
			{
				System.out.println("Unable to close " + getUsername() + " connection.");
				//ioe.printStackTrace();
			}
			
			System.out.println(getUsername() + " is not active.");
			return false;
		}
	}
	
	//Send Player Status To Client
	public void sendStatus()
	{
		try
		{
			int temp = getStatus();
			toPlayer.writeInt(temp);
			toPlayer.flush();
		}
		catch(IOException e)
		{
			System.out.println("Unable to send status to " + getUsername() + ".");
			//e.printStackTrace();
		}
	}
	
	//Send Dealer Info (Card Drawn or Card Total)
	public void sendDealerInfo(String data, boolean isCard)
	{
		try
		{
			String message = ((isCard)? 'C' : 'T') + data;
			toPlayer.writeUTF(message);
			toPlayer.flush();
		}
		catch(IOException e)
		{
			System.out.println("Unable to send dealer info to " + getUsername() + ".");
			//e.printStackTrace();
		}
	}
	
	//Send Dealer Info (Card Drawn)
	public void sendDealerInfo(String data)
	{
		try
		{
			toPlayer.writeUTF(data);
			toPlayer.flush();
		}
		catch(IOException e)
		{
			System.out.println("Unable to send dealer info to " + getUsername() + ".");
			//e.printStackTrace();
		}
	}
	
	//Send Winner Username & Best Score
	public void sendWinnerInfo(String username, int score)
	{
		try
		{
			toPlayer.writeUTF(username);
			toPlayer.writeInt(score);
			toPlayer.flush();
		}
		catch(IOException e)
		{
			System.out.println("Unable to send winner info to " + getUsername() + ".");
			//e.printStackTrace();
		}
	}
	
	//Send Server-Side Trial Number Counter To Observer
	public void sendTrialInfo(int trialNum)
	{
		try
		{
			toPlayer.writeInt(trialNum);
		}
		catch(IOException e)
		{
			System.out.println("Unable to send trial info to " + getUsername() + ".");
			//e.printStackTrace();
		}
	}
	
	//Send Ranking Info To Player
	//Client's Expected Output From Server: Vector<Vector<String>>
	public void sendRanking(Map<String,Integer> rankMap)
	{
		//Adapt Map Rankings To Vector
		Vector<Vector<String>> rankVector = new Vector<Vector<String>>();			
		Set<String> keys = rankMap.keySet();
		Iterator<String> ite = keys.iterator();
		
		while(ite.hasNext())
		{
			String key = ite.next();
			Vector<String> temp = new Vector<String>();
			temp.add(key);
			temp.add(Integer.toString(rankMap.get(key)));
			rankVector.add(temp);
		}
		
		//Send Vector To Client
		try
		{
			objectToPlayer.writeObject(rankVector);
			objectToPlayer.flush();
		}
		catch (IOException e)
		{
			System.out.println("Unable to send rankings to " + getUsername() + ".");
			//e.printStackTrace();
		}		
	}
	
	
	
	/*************** INHERITED METHODS ***************/
	
	//Reset Counters & Variables Before Starting A New Trial
	public void resetGame()
	{		
		currentHand = 0;
		totalHands = 1;
		setBestChance(0);
		
		for(int i=0; i<MAX_HANDS; i++)
		{
			deckTotal[i] = 0;
			totalCards[i] = 0;
			
			for(int j=0; j<MAX_CARDS; j++)
				cards[i][j] = null;
		}		
	}
	
	//Get Current Card Deck
	public String[] getCards()
	{
		return cards[currentHand];
	}
	
	//Set Initial State of Trial: Drawn 2 Random Cards
	public void initGame(CardsCollection cc, String card1, String card2)
	{
		cards[0][0] = card1;
		cards[0][1] = card2;
		totalCards[0] = 2;
		deckTotal[0] = cc.countTotal(cards[0]);
	}
	
	//Signal That Game Is Starting
	//Send 3 Initial Info: Dealer's 1st Card & Player's Randomly Drawn Cards
	public void startGame(String dealerCard)
	{
		try
		{
			setStatus(INGAME_WAIT);
			toPlayer.writeInt(INGAME_WAIT);
			toPlayer.writeUTF(dealerCard);
			toPlayer.writeUTF(cards[0][0]);
			toPlayer.writeUTF(cards[0][1]);
			toPlayer.flush();
		}
		catch(IOException ie)
		{
			System.out.println("Unable to start a game for " + getUsername() + ".");
			//ie.printStackTrace();
		}
	}
	
	//Signal For Next Move
	public int nextMove()
	{
		//Declare Variables
		boolean valid;
		int response = ACT_WITHDRAW;
		
		try
		{
			//Signal Player Turn
			setStatus(INGAME_MYTURN);
			toPlayer.writeInt(INGAME_MYTURN);
			toPlayer.flush();
			do
			{	
				//Read & Validate Client Response Within 15 Seconds
				socket.setSoTimeout(TIMEOUT);
				response = fromPlayer.readInt();
				
				socket.setSoTimeout(0);
				
				if(response == ACT_STAND)
					valid = CardsCollection.isStandable(cards[currentHand]);
				else if(response == ACT_SPLIT)
					valid = (CardsCollection.isSplitable(cards[currentHand]) && (totalHands == 1));
				else
					valid = (response == ACT_HIT);
				
				toPlayer.writeBoolean(valid);
				toPlayer.flush();
			}
			while(!valid);
			
			return response;
		}
		catch (SocketTimeoutException e)
		{
			setStatus(INGAME_WITHDRAW);
			currentHand++; //Switch hand if has more than 1 hand
			
			try
			{
				toPlayer.writeInt(INGAME_WITHDRAW);
				toPlayer.flush();
			}
			catch (IOException ioe)
			{
				//ioe.printStackTrace();
			}
			
			//e.printStackTrace();
			return response;
		}
		catch(Exception e)
		{
			setStatus(INGAME_WITHDRAW);
			currentHand++;
			System.out.println("Unable to get response from " + getUsername() + ".");
			//e.printStackTrace();
			return response;
		}
	}
	
	//Check Whether Has Next Move To Make
	public boolean hasNextMove()
	{
		if(getStatus() == INGAME_WAIT)
		{
			if(deckTotal[currentHand] == MAX_POINTS)
			{
				//Initial 2 Cards Brings Blackjack
				setStatus(INGAME_STAND);
				setBestChance(deckTotal[currentHand]);				
				return false;
			}
			else
				return true;
		}
		else if(getStatus() == INGAME_CONTINUE)
			return true;
		else
		{	
			//Allow Switch to Next Available Hand For Withdraw/Stand/Burst Status
			//No More Move If No More Hands
			return (currentHand < totalHands);
		}
	}
	
	//Hit Action Process
	public void hit(CardsCollection cc, String card)
	{
		int totalInDeck = totalCards[currentHand];
		int result;
		
		if(totalInDeck < MAX_CARDS)
		{
			//Increment Counters
			cards[currentHand][totalInDeck] = card;
			totalCards[currentHand] = totalInDeck + 1;
			deckTotal[currentHand] = cc.countTotal(cards[currentHand]);
			
			//Check New Status
			if(deckTotal[currentHand] > MAX_POINTS)
				result = INGAME_BURST;
			else if((totalCards[currentHand] == MAX_CARDS) || (deckTotal[currentHand] == MAX_POINTS))
				result = INGAME_STAND;
			else
				result = INGAME_CONTINUE;
			
			try
			{
				//Set New Player Status
				setStatus(result);
				if(result == INGAME_STAND && (deckTotal[currentHand] > getBestChance()))
					setBestChance(deckTotal[currentHand]);
				toPlayer.writeUTF(card);
				toPlayer.writeInt(result);
				toPlayer.flush();
			}
			catch(IOException e)
			{
				System.out.println("Unable to send new card info to " + getUsername() + ".");
				//e.printStackTrace();
			}			

			//Switch hand if burst/stand and has more than 1 hand.
			//If no hands available, it will be detected in hasNextMove()
			if((result == INGAME_BURST) || (result == INGAME_STAND))
				currentHand++; 
							   
		}
	}
	
	//Stand Action Process
	public void stand()
	{
		if(CardsCollection.isStandable(cards[currentHand]))
		{
			setStatus(INGAME_STAND);
			if(deckTotal[currentHand] > getBestChance())
				setBestChance(deckTotal[currentHand]);
			currentHand++; //Switch hand if has more than 1 hand
						   //If no hands available, it will be detected in hasNextMove()
			
			try
			{
				toPlayer.writeInt(INGAME_STAND);
			}
			catch(IOException e)
			{
				System.out.println("Unable to send stand status to " + getUsername() + ".");
				//e.printStackTrace();
			}
		}
	}
	
	//Split Action Process
	public void split(CardsCollection cc)
	{
		if(CardsCollection.isSplitable(cards[0]) && totalHands == 1)
		{
			//Move 2nd Card to 2nd Deck
			cards[1][0] = cards[0][1];
			cards[0][1] = null;
			
			//Update Counters
			totalHands++;
			totalCards[0] = totalCards[0] - 1;
			totalCards[1] = totalCards[1] + 1;
			//deckTotal[0] = cc.countTotal(cards[0]);
			//deckTotal[1] = cc.countTotal(cards[1]);
			deckTotal[0] = deckTotal[0] / 2;
			deckTotal[1] = deckTotal[0];
			
			try
			{
				//Update Status
				setStatus(INGAME_CONTINUE);
				toPlayer.writeInt(INGAME_CONTINUE);
				toPlayer.flush();
			}
			catch(IOException e)
			{
				System.out.println("Unable to send split status to " + getUsername() + ".");
				//e.printStackTrace();
			}
		}
	}
	
	//Get Counting Total of Current Deck (Which May Be Burst Score)
	public int getCurrentChance()
	{
		return deckTotal[currentHand];
	}
}
